#!/usr/bin/python3
"""
Configure dracut.

The 'config' option allows to create a dracut configuration file under
`/usr/lib/dracut/dracut.conf.d/` with the name `filename`. Only a subset
of configuration options is supported, with the intention to provide
functional parity with `org.osbuild.dracut` stage.

Constrains:
  - At least one configuration option must be specified for each configuration

Supported configuration options:
  - compress
  - dracutmodules
  - add_dracutmodules
  - omit_dracutmodules
  - drivers
  - add_drivers
  - force_drivers
  - filesystems
  - install_items
  - early_microcode
  - reproducible
"""

import sys

import osbuild.api


SCHEMA = r"""
"additionalProperties": false,
"required": ["config", "filename"],
"properties": {
  "filename": {
    "type": "string",
    "description": "Name of the dracut configuration file.",
    "pattern": "^[\\w.-]{1,250}\\.conf$"
  },
  "config": {
    "additionalProperties": false,
    "type": "object",
    "description": "dracut configuration.",
    "minProperties": 1,
    "properties": {
      "compress": {
        "description": "Compress the generated initramfs using the passed compression program.",
        "type": "string"
      },
      "dracutmodules": {
        "description": "Exact list of dracut modules to use.",
        "type": "array",
        "items": {
          "type": "string",
          "description": "A dracut module, e.g. base, nfs, network ..."
        }
      },
      "add_dracutmodules": {
        "description": "Additional dracut modules to include.",
        "type": "array",
        "items": {
          "type": "string",
          "description": "A dracut module, e.g. base, nfs, network ..."
        }
      },
      "omit_dracutmodules": {
        "description": "Dracut modules to not include.",
        "type": "array",
        "items": {
          "type": "string",
          "description": "A dracut module, e.g. base, nfs, network ..."
        }
      },
      "drivers": {
        "description": "Kernel modules to exclusively include.",
        "type": "array",
        "items": {
          "type": "string",
          "description": "A kernel module without the .ko extension."
        }
      },
      "add_drivers": {
        "description": "Add a specific kernel modules.",
        "type": "array",
        "items": {
          "type": "string",
          "description": "A kernel module without the .ko extension."
        }
      },
      "force_drivers": {
        "description": "Add driver and ensure that they are tried to be loaded.",
        "type": "array",
        "items": {
          "type": "string",
          "description": "A kernel module without the .ko extension."
        }
      },
      "filesystems": {
        "description": "Kernel filesystem modules to exclusively include.",
        "type": "array",
        "items": {
          "type": "string",
          "description": "A kernel module without the .ko extension."
        }
      },
      "install_items": {
        "description": "Install the specified files.",
        "type": "array",
        "items": {
          "type": "string",
          "description": "Specify additional files to include in the initramfs."
        }
      },
      "early_microcode": {
        "description": "Combine early microcode with the initramfs.",
        "type": "boolean"
      },
      "reproducible": {
        "description": "Create reproducible images.",
        "type": "boolean"
      }
    }
  }
}
"""


def bool_to_string(value):
    return "yes" if value else "no"


# Writes to a given file option with the following format:
# persistent_policy="<policy>"
def string_option_writer(f, option, value):
    f.write(f'{option}="{value}"\n')


# Writes to a given file option with the following format:
# add_dracutmodules+=" <dracut modules> "
def list_option_writer(f, option, value):
    value_str = " ".join(value)
    f.write(f'{option}+=" {value_str} "\n')


# Writes to a given file option with the following format:
# reproducible="{yes|no}"
def bool_option_writer(f, option, value):
    f.write(f'{option}="{bool_to_string(value)}"\n')


def main(tree, options):
    config = options["config"]
    filename = options["filename"]

    config_files_dir = f"{tree}/usr/lib/dracut/dracut.conf.d"

    SUPPORTED_OPTIONS = {
        # simple string options
        "compress": string_option_writer,
        # list options
        "add_dracutmodules": list_option_writer,
        "dracutmodules": list_option_writer,
        "omit_dracutmodules": list_option_writer,
        "drivers": list_option_writer,
        "add_drivers": list_option_writer,
        "force_drivers": list_option_writer,
        "filesystems": list_option_writer,
        "install_items": list_option_writer,
        # bool options
        "early_microcode": bool_option_writer,
        "reproducible": bool_option_writer
    }

    with open(f"{config_files_dir}/{filename}", "w") as f:
        for option, value in config.items():
            try:
                writter_func = SUPPORTED_OPTIONS[option]
                writter_func(f, option, value)
            except KeyError as e:
                raise ValueError(f"unsupported configuration option '{option}'") from e


if __name__ == '__main__':
    args = osbuild.api.arguments()
    r = main(args["tree"], args["options"])
    sys.exit(r)
